#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "../include/ioctl.h"
#include <unistd.h>


#define ERASE_PAGE_SIZE	0x200

#define FPCTL	0x02
#define FPDAT  	0xB4

#define FLASH_SIZE (16*1024)
unsigned char c2_read_address(int fd);
int poll_inbusy(int fd)
{
	int i=10;
	unsigned char t;
	while(i--)
	{
		t=c2_read_address(fd);
		if(!(t&0x02)) return 0;
	}
	return 1;
}

int poll_outready(int fd)
{
	int i=10;
	unsigned char t;
	while(i--)
	{
		t=c2_read_address(fd);
		if(t&0x01) return 0;
	}
	return 1;
}

unsigned char c2_read_address(int fd)
{
	unsigned char addr = 0;
	if(ioctl(fd, C2_READ_ADDRESS, &addr))
	{
		printf("C2_READ_ADDRESS failed\n");
		return 0;
	}
	return addr;
}

void c2_write_address(int fd, unsigned char addr)
{
	if(ioctl(fd, C2_WRITE_ADDRESS, addr))
	{
		printf("C2_WRITE_ADDRESS failed\n");
		return;
	}
}

unsigned char c2_read_data(int fd)
{
	unsigned char data = 0;
	if(ioctl(fd, C2_READ_DATA, &data))
	{
		printf("C2_READ_DATA failed\n");
		return 0;
	}
	return data;
}

void c2_write_data(int fd, unsigned char data)
{
	if(ioctl(fd, C2_WRITE_DATA, data))
	{
		printf("C2_WRITE_DATA failed\n");
		return;
	}
}

int c2_enter_device(int fd)
{
	ioctl(fd, C2_RESET, 0x00);

	c2_write_address(fd, FPCTL);
	c2_write_data(fd, 0x02);
	c2_write_data(fd, 0x01);
	usleep(100000);
	return 0;
}

int c2_erase_device(int fd)
{
	unsigned char cmd_seq[] = {0xde, 0xad, 0xa5};
	int i = 0;
	unsigned char status;

	c2_enter_device(fd);

	c2_write_address(fd, FPDAT);
	c2_write_data(fd, 0x03);

	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after DEVICE_ERASE \n");
		return 1;
	}
	if(poll_outready(fd))
	{
		printf("outready not set after DEVICE_ERASE \n");
		return 1;
	}

	status = c2_read_data(fd);
	if(status != 0x0d)
	{
		printf("status error after DEVICE_ERASE - %02X\n", status);
		return 1;
	}

	for(i=0;i<sizeof(cmd_seq);i++)
	{
		c2_write_data(fd, cmd_seq[i]);
		if(poll_inbusy(fd))
		{
			printf("error occured while writing cmd_seq - inbusy not clear\n");
			return 1;
		}
	}

	i=200;
	while(i--)
	{
		if(!poll_outready(fd)) break;
		usleep(10000);
	}
	if(!i)
	{
		printf("outready not set after DEVICE_REASE cmd_seq\n");
		return 1;
	}

	status = c2_read_data(fd);
	if(status != 0x0d)
	{
		printf("status error after DEVICE_REASE cmd_seq - %02X\n", status);
		return 1;
	}

	return 0;

}

int c2_block_write(int fd, unsigned short address, unsigned char len, unsigned char* data)
{
	unsigned char status;
	c2_enter_device(fd);

	c2_write_address(fd, FPDAT);
	c2_write_data(fd, 0x07);
	
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after BLOCK_WRITE \n");
		return 1;
	}
	if(poll_outready(fd))
	{
		printf("outready not set after BLOCK_WRITE \n");
		return 1;
	}

	status = c2_read_data(fd);
	if(status != 0x0d)
	{
		printf("status error after BLOCK_WRITE - %02X\n", status);
		return 1;
	}

	c2_write_data(fd, address>>8);
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after writing high address byte\n");
		return 1;
	}

	c2_write_data(fd, address);
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after writing low address byte\n");
		return 1;
	}


	c2_write_data(fd, len);
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after writing length\n");
		return 1;
	}

	if(poll_outready(fd))
	{
		printf("outready not set after writing lenght \n");
		return 1;
	}

	status = c2_read_data(fd);
	if(status != 0x0d)
	{
		printf("status error after writing length - %02X\n", status);
		return 1;
	}

	unsigned truelen = len==0?256:len;
	int i;
	for(i=1;i<=truelen;i++)
	{
		c2_write_data(fd, data[i-1]);
		if(poll_inbusy(fd))
		{
			printf("inbusy not clear after writing %dth byte, total %d bytes\n", i, truelen);
			return 1;
		}

	}
	return 0;
}

int c2_block_read(int fd, unsigned short address, unsigned char len, unsigned char* data)
{
	unsigned char status;

	c2_enter_device(fd);
	
	c2_write_address(fd, FPDAT);
	c2_write_data(fd, 0x06);
	
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after BLOCK_READ \n");
		return 1;
	}
	if(poll_outready(fd))
	{
		printf("outready not set after BLOCK_READ \n");
		return 1;
	}

	status = c2_read_data(fd);
	if(status != 0x0d)
	{
		printf("status error after BLOCK_READ - %02X\n", status);
		return 1;
	}

	c2_write_data(fd, address>>8);
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after writing high address byte\n");
		return 1;
	}

	c2_write_data(fd, address);
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after writing low address byte\n");
		return 1;
	}


	c2_write_data(fd, len);
	if(poll_inbusy(fd))
	{
		printf("inbusy not clear after writing length\n");
		return 1;
	}

	if(poll_outready(fd))
	{
		printf("outready not set after writing lenght \n");
		return 1;
	}

	status = c2_read_data(fd);
	if(status != 0x0d)
	{
		printf("status error after writing length - %02X\n", status);
		return 1;
	}

	unsigned truelen = len==0?256:len;
	int i;

	for(i=1;i<truelen;i++)
	{
		if(poll_outready(fd))
		{
			printf("outready not set when reading %dth byte, total %d bytes\n", i, truelen);
			return 1;
		}

		data[i-1] = c2_read_data(fd);

	}
	return 0;
}

void usage()
{
	printf("usage: c2flash <file>\n\n");
}

int load_file(char* filename, unsigned char * buf)
{
	FILE *fp = fopen(filename, "r");
	if(NULL==fp) return 0;
	
	int len = fread(buf, 1, FLASH_SIZE, fp);
	fclose(fp);
	return len;
}

int main(int argc, char* argv[])
{
	if(argc!=2)
	{
		usage();
		return 1;
	}

	int fd = open("/dev/c2", O_RDONLY);
	if(-1==fd)
	{
		printf("open /dev/c2 failed!\n");
		return 1;
	}

	unsigned char data = 0;	
	ioctl(fd, C2_RESET, 0x00);
	c2_write_address(fd, 0);
	printf("DEVICEID: %02X\n", c2_read_data(fd));
	c2_write_address(fd, 1);
	printf("REVID: %02X\n", c2_read_data(fd));


	unsigned char *databuf = malloc(FLASH_SIZE);
	int len = load_file(argv[1], databuf);
	if(len<=0)
	{
		printf("can not open file - %s\n", argv[1]);
		return 1;
	}

	printf("data length: %d\n", len);
	printf("Eraseing device ...\n");

	if(c2_erase_device(fd))
	{
		printf("Failed!\n\n");
		return 1;
	}
	printf("Done!\n\n");

	printf("Programing ...\n");

	int towrite, writed = 0;
	while(len > 0)
	{
		towrite = len>256? 256 : len;
		if(c2_block_write(fd, writed, towrite%256, databuf+writed))
		{
			printf("Failed\n\n");
			return 1;
		}

		writed += towrite;
		len -= towrite;
	}

	printf("Done\n");

	ioctl(fd, C2_RESET, 0x00);
	return 0;
}

